#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <functional>

using namespace std;

namespace Server
{
  static const string defaultIp = "0.0.0.0"; // 自定义ip，默认ip，一款服务器不能只绑定一个ip因为这样会导致客户端无法用其它ip访问
  enum  //枚举错误类型
  {
    USAGE_ERR = 1,
    SOCKET_ERR = 2,
    BIND_ERR,
    OPEN_ERR
  };
  static const int NUM = 1024;
  using func_t = function<void(int, string, uint16_t, string)>;
  class udpServer
  {
  public:
    udpServer(func_t func, const uint16_t &port, const string ip = defaultIp) : func(func), _port(port), _ip(ip), _sockfd(-1)
    {
    } // 初始化
    void initServer()
    { // 1.创建套接字 socket
      // 2.创建网络套接字的结构体  struct sockaddr_in ，然后对结构体初始化（bzero），填充
      // 3.将套接字绑定上ip和端口号 bind
      _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
      if (_sockfd == -1)//判断创建成功与否
      {
        cerr << "socket error:" << errno << " : " << strerror(errno) << endl;
        exit(SOCKET_ERR);
      }
      cout << "socket success: " << " : " << _sockfd << endl;
      struct sockaddr_in local;//文件描述符绑定它的ip和端口
      bzero(&local, sizeof(local));                                    // 初始化local，全部变为0
      local.sin_family = AF_INET;                                      // 设置首两个字节决定它的套接字类型
      local.sin_port = htons(_port);                                   // 端口号的转入
      local.sin_addr.s_addr = inet_addr(_ip.c_str());                  // 将点分十进制转化为整型，以及本地转网络，这个ip用0.0.0.0是为了便于用于各种对这个服务器并且对应port发更加方便。
      local.sin_addr.s_addr=htonl(INADDR_ANY);//绑定全0，就可以所有ip都可以访问
      int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local)); // 将网络套接字与网络文件的结构体绑定
      if (n == -1)
      {
        cerr << "bind error:" << errno << ":" << strerror(errno) << endl;
        exit(BIND_ERR);
      }
    }
    void start()
    {
      // 服务器的本质就是个死循环，操作系统就是死循环
      char buffer[NUM]; // 用于接收客户端发的信息
      for (;;)
      { // 因此不能有内存泄露
        // 1.创建客户端的结构体（输出型参数，辨别那一个客户端发的数据）
        // 2.读客户端发的数据：数据是什么，谁发出的
        struct sockaddr_in peer; // 创建结构体用来获取客户端信息
        socklen_t len = sizeof(peer);
        ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len); // 获取客户端发的消息
        if (s > 0)
        {
          buffer[s] = 0;
          string clientip = inet_ntoa(peer.sin_addr); // 1.网络序列 2.int->点分十进制IP,获取客户端的ip
          uint16_t clientport = ntohs(peer.sin_port); // 获取端口号
          string message = buffer;

          cout << clientip << "[" << clientport << "]# " << message << endl;
          func(_sockfd, clientip, clientport, message);
        }
      }
    }
    ~udpServer()
    {
    }

  private:
    uint16_t _port; // 端口号
    string _ip;     // 点分十进制的IP-字符串，可读性好，uint32_t ip:整数风格的IP-网络通信使用
    int _sockfd;    // 套接字的描述符类似于文件描述符
    func_t func;
  };
}